import json
import os
import random
import time

import parsel
import requests as req
from lxml import etree
from ofunctions.network import set_ip_version
from tqdm import tqdm

set_ip_version(4)

config_path = os.path.join(os.path.dirname(__file__), 'resource/config.json')
# 读取配置文件
config_file = open(config_path, 'r', encoding='utf-8')

config_json = json.load(config_file)

dict_data = config_json.get('font_dict')

title_font_dict = config_json.get('title_font_dict')

headers_lib = config_json.get('headers_lib')


def get_cookie(chapter_link, t=0):
    """
    获取用于访问章节链接的cookie。

    本函数尝试找到一个合适的cookie值，用于访问给定的章节链接。它通过模拟随机的用户ID来寻找这个值，
    直到找到一个使得访问章节内容长度超过200字符的cookie为止。如果给定了一个特定的cookie值，它会尝试
    使用这个值来访问章节内容。

    参数:
    chapter_link (str): 章节的链接。
    t (int, 可选): 默认为0，用于指定一个特定的cookie值。

    返回:
    str: 如果成功找到一个合适的cookie并且能够访问章节内容，返回's'。如果给定了一个特定的cookie值但
         仍然无法访问章节内容，返回'err'。
    """
    global cookie
    # 如果t为默认值0，表示需要通过遍历随机ID来寻找合适的cookie
    if t == 0:
        # 遍历一个巨大的随机ID范围
        for i in range(random.randint(1000000000000000000 * 6, 1000000000000000000 * 8), 1000000000000000000 * 9):
            # 模拟随机延时，以增加随机性
            time.sleep(random.randint(50, 150) / 1000)
            # 生成一个基于随机ID的cookie值
            cookie = 'novel_web_id=' + str(i)
            # 如果使用当前cookie值访问章节链接返回的内容长度超过200，认为找到了合适的cookie
            if len(down_text(chapter_link)) > 200:
                print(f'找到合适的cookie：{cookie}')
                config_json['cookie'] = cookie
                with open(config_path, 'w', encoding='utf-8') as f:
                    json.dump(config_json, f)
                return 's'
    else:
        # 如果t不为0，表示已经给定了一个特定的cookie值，尝试使用这个值
        cookie = t
        # 如果使用给定的cookie值访问章节链接返回的内容长度超过200，认为找到了合适的cookie
        if len(down_text(chapter_link)) > 200:
            print(f'使用给定的cookie值：{cookie}')
            return 's'
        else:
            # 如果内容长度不超过200，表示给定的cookie值无效，返回错误信息
            return 'err'


def get_random_header():
    """
    获取一个随机的头部信息。

    随机选择一个预定义的头部，并将全局变量中的cookie添加到该头部中。

    Returns:
        dict: 一个带有随机选择的头部信息和cookie的字典。
    """
    # 随机选择一个头部
    headers = headers_lib[random.randint(0, len(headers_lib) - 1)]
    # 给头部添加cookie信息
    headers['cookie'] = cookie
    return headers


def str_interpreter(content, mode=0):
    """
    将输入的字符串通过特定规则进行解释转换。

    该函数遍历输入的字符串，利用字典`dict_data`将字符串中的某些字符转换为特定的字符串。
    如果字符在字典中找不到对应的条目，则将原始字符添加到结果字符串中。

    参数:
    content (str): 需要解释的输入字符串。

    返回:
    str: 解释转换后的字符串。
    """
    # 初始化空字符串，用于存储解释后的结果
    s = ''

    font_dict = dict_data
    if mode == 1:
        font_dict = title_font_dict

    # 遍历输入字符串的每一个字符
    for index in content:
        # 将字符转换为ASCII码，并通过ASCII码从字典中获取对应的条目
        it = font_dict.get(str(ord(index)))

        # 如果在字典中找到了对应的条目
        if it:
            # 将找到的条目添加到结果字符串中
            s += it
        else:
            # 如果没有找到对应的条目，则将原始字符添加到结果字符串中
            s += index

    # 返回解释转换后的字符串
    return s


def down_text(chapter_link):
    """
    根据章节链接下载小说内容。

    参数:
    chapter_link (str): 小说章节的链接后缀。

    返回:
    str: 下载的小说章节内容，如果下载失败则返回'下载失败'。
    """
    # 拼接完整的小说章节链接
    link_url = 'https://fanqienovel.com' + chapter_link
    # 循环尝试请求链接直到成功
    while True:
        try:
            # 发送HTTP请求并获取小说章节的数据内容
            link_data = req.get(url=link_url, headers=get_random_header()).text
            # 使用parsel解析数据，提取小说内容
            link_selector = parsel.Selector(link_data)
            # 提取小说内容文本列表
            content_list = link_selector.css('.muye-reader-content-16 p::text').getall()
            break
        except:
            # 请求失败时，暂停0.5秒后重试
            time.sleep(0.5)
    # 尝试将内容列表合并成字符串并进行特殊字符处理
    try:
        return str_interpreter('\n'.join(content_list))
    except:
        # 如果处理失败，则返回'下载失败'
        return '下载失败'


def get_book_info(book_id):
    """
    根据书籍ID获取书籍信息。

    参数:
    book_id (int): 书籍ID。

    返回:
    list: 包含书名、章节字典、标签列表、简介、作者和字数的列表。
    """
    # 初始化章节字典，用于存储章节标题和链接
    chapter_dict = {}

    # 发送HTTP请求获取书籍页面内容，使用随机用户代理头
    ele = etree.HTML(req.get('https://fanqienovel.com/page/' + str(book_id), headers=get_random_header()).text)

    # 提取章节信息
    a = ele.xpath('//div[@class="chapter"]/div/a')
    for i in range(len(a)):
        chapter_dict[a[i].text] = a[i].xpath('@href')[0]

    # 检查是否存在书名，如果不存在则返回错误标记
    if not ele.xpath('//h1/text()'):
        return ['err', 0, 0]

    # 提取书名
    book_name = sanitize_filename(ele.xpath('//h1/text()')[0])

    # 提取简介
    abstract = ele.xpath('//div[@class="page-abstract-content"]/p/text()')[0]

    # 提取标签
    tag_list = ele.xpath('//span[@class="info-label-yellow"]/text()')[0]

    # 提取作者
    author = ele.xpath('//span[@class="author-name-text"]/text()')[0]

    # 提取字数
    count = ele.xpath('//div[@class="info-count-word"]/span[@class="detail"]/text()')[0] + ele.xpath(
        '//div[@class="info-count-word"]/span[@class="text"]/text()')[0]

    # 返回书籍信息
    return [book_name, chapter_dict, tag_list, abstract, author, count]


def down_book(book_id):
    # 含书名、章节字典、标签列表、简介、作者、字数
    book_info = get_book_info(book_id)
    chapter_dict = book_info[1]
    with open(f'resource/book/{book_info[0]}.txt', 'w', encoding='utf-8') as f:
        with tqdm(total=len(chapter_dict.keys()), desc='正在下载', unit='章') as pbar:
            f.write(book_info[0] + '\n\n')
            f.write('作者：' + book_info[4] + '\n')
            f.write('简介：' + book_info[3] + '\n\n')
            for title, link in chapter_dict.items():
                content = down_text(link)
                f.write(title + '\n')
                f.write(content + '\n\n')
                pbar.set_description(f'正在下载：{book_info[0]} {title}')
                pbar.update(1)


def sanitize_filename(filename):
    """
    清理文件名中的非法字符。

    在一些操作系统中，文件名中包含特定的字符是不被允许的。
    此函数会将这些非法字符替换为它们的全角形式，以确保文件名合法。

    参数:
    filename (str): 待清理的文件名。

    返回:
    str: 清理后的文件名。
    """

    # 定义非法字符列表
    illegal_chars = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
    # 定义替换字符列表
    illegal_chars_rep = ['＜', '＞', '：', '＂', '／', '＼', '｜', '？', '＊']

    # 遍历非法字符列表，逐个替换
    for i in range(len(illegal_chars)):
        # 替换非法字符为对应的全角字符
        filename = filename.replace(illegal_chars[i], illegal_chars_rep[i])

    return filename


def search(key, page_size=10, page_index=0):
    """
    根据关键词搜索书籍。

    该函数通过发送HTTP GET请求到指定的API端点，根据提供的关键词搜索书籍。
    它允许分页显示结果，通过page_size和page_index参数控制每页显示的数量和当前页数。

    参数:
    - key: str, 搜索关键词。
    - page_size: int, 每页显示的结果数量。默认为10。
    - page_index: int, 当前页数。默认为0。

    返回:
    - list: 搜索结果列表，包含书籍信息。
    - str: 如果发生异常，返回错误标识'err'。
    """
    try:
        # 构建搜索请求的URL，包含关键词
        url = f'https://api5-normal-lf.fqnovel.com/reading/bookapi/search/page/v/?query={key}&aid=1967&channel=0&os_version=0&device_type=0&device_platform=0&iid=466614321180296&passback=0&version_code=999'

        # 发送GET请求，使用随机请求头
        response = req.get(url, headers=get_random_header())

        # 解析响应，返回搜索结果列表
        return response.json()['data']
    except:
        # 异常处理，返回错误标识
        return 'err'


def search_book_list():
    while True:
        key = input("请输入搜索关键词（直接Enter返回）：")
        if key == '':
            return 'b'
        response = req.get(f'https://fanqienovel.com/api/author/search/search_book/v1?'
                           f'filter=127,127,127,127&page_count=10&page_index=0&query_type=0&query_word={key}',
                           headers=get_random_header())
        books = response.json()['data']['search_book_data_list']

        for i, book in enumerate(books):
            print(
                f"{i + 1}. 名称：{str_interpreter(book['book_name'], 1)} 作者：{str_interpreter(book['author'], 1)} ID：{book['book_id']} 字数：{book['word_count']}")

        while True:
            choice_ = input("请选择一个结果, 输入 r 以重新搜索：")
            if choice_ == "r":
                break
            elif choice_.isdigit() and 1 <= int(choice_) <= len(books):
                chosen_book = books[int(choice_) - 1]
                return chosen_book['book_id']
            else:
                print("输入无效，请重新输入。")
